using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.Diagnostics;

namespace Qreed.Windows
{
    [DebuggerDisplay("DisplayName = {DisplayName}")]
    public class ShellPIDL : IDisposable
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ShellPIDL"/> class.
        /// </summary>
        /// <param name="pIDL">The PIDL.</param>
        public ShellPIDL(IntPtr pIDL)
        {
            _pIDL = pIDL;

            Initialize();
        }

        internal ShellPIDL(ShellPIDL parent, IntPtr pIDL)
        {
            _pIDL = Shell32.ILCombine(parent.PIDL, pIDL);
            Initialize();
        }


        /// <summary>
        /// Creates a new ShellPIDL from the given path.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns></returns>
        public static ShellPIDL FromPath(string path)
        {
            if (string.Compare(path, ShellPIDL.DesktopPIDL.Path) == 0)
                return ShellPIDL.DesktopPIDL;

            IntPtr ptr = ShellPIDL.GetPIDL(path);

            return new ShellPIDL(ptr);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ShellPIDL"/> class.
        /// </summary>
        /// <param name="CSIDL">The CSIDL.</param>
        internal ShellPIDL(Shell32.CSIDL CSIDL)
        {
            int res = Shell32.SHGetSpecialFolderLocation(IntPtr.Zero, CSIDL, ref _pIDL);
            if (res != 0) Marshal.ThrowExceptionForHR(res);

            Initialize();
        }

        private void Initialize()
        {
            Shell32.SHFILEINFO fileInfo = new Shell32.SHFILEINFO();

            Shell32.SHGFI flags = Shell32.SHGFI.SHGFI_PIDL | Shell32.SHGFI.SHGFI_DISPLAYNAME | 
                                  Shell32.SHGFI.SHGFI_ATTRIBUTES | Shell32.SHGFI.SHGFI_TYPENAME |
                                  Shell32.SHGFI.SHGFI_SMALLICON | Shell32.SHGFI.SHGFI_SYSICONINDEX;

            IntPtr ret = Shell32.SHGetFileInfo(_pIDL, 0, out fileInfo, (uint)Marshal.SizeOf(fileInfo), flags);

            _displayName = fileInfo.szDisplayName;
            _typeName = fileInfo.szTypeName;
            _attributes = fileInfo.dwAttributes;
            _iconIndex = fileInfo.iIcon;
            
            // Get the index of the selected icon

            fileInfo = new Shell32.SHFILEINFO();

            flags = Shell32.SHGFI.SHGFI_PIDL | 
                    Shell32.SHGFI.SHGFI_SMALLICON | 
                    Shell32.SHGFI.SHGFI_SYSICONINDEX |
                    Shell32.SHGFI.SHGFI_OPENICON;

            Shell32.SHGetFileInfo(_pIDL, 0, out fileInfo, (uint)Marshal.SizeOf(fileInfo), flags);

            _selectedIconIndex = fileInfo.iIcon;
        }

        private int _iconIndex;
        /// <summary>
        /// Gets the index in the system imagelist of the small icon.
        /// </summary>
        /// <value>The index of the icon.</value>
        public int IconIndex
        {
            get { return _iconIndex; }
        }

        private int _selectedIconIndex;
        /// <summary>
        /// Gets the index in the system imagelist of the small selected icon.
        /// </summary>
        /// <value>The index of the selected icon.</value>
        public int SelectedIconIndex
        {
            get { return _selectedIconIndex; }
        }

        private string _displayName;
        /// <summary>
        /// Gets the display name.
        /// </summary>
        /// <value>The display name.</value>
        public string DisplayName
        {
            get { return _displayName; }
        }

        private string _typeName;
        /// <summary>
        /// Gets the name of the type.
        /// </summary>
        /// <value>The name of the type.</value>
        public string TypeName
        {
            get { return _typeName; }
        }

        private Shell32.SFGAO _attributes = 0;
        /// <summary>
        /// Gets the attributes.
        /// </summary>
        /// <value>The attributes.</value>
        internal Shell32.SFGAO Attributes
        {
            get { return _attributes; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is folder.
        /// </summary>
        /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
        public bool IsFolder
        {
            get{return (_attributes & Shell32.SFGAO.SFGAO_FOLDER) != 0;}
        }

        /// <summary>
        /// Gets a value indicating whether this instance has sub folders.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has sub folders; otherwise, <c>false</c>.
        /// </value>
        public bool HasSubFolders
        {
            get { return (_attributes & Shell32.SFGAO.SFGAO_HASSUBFOLDER) != 0; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is desktop.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is desktop; otherwise, <c>false</c>.
        /// </value>
        public bool IsDesktop
        {
            get
            {
                return this == DesktopPIDL || this.DisplayName == "Desktop";
                       //ShellFolder.CompareIDs(Shell32.SHCIDS.SHCIDS_ALLFIELDS, _pIDL, DesktopPIDL.PIDL) == 0; // Oh dear .. this can cause an stack overflow
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool d)
        {
            if (d)
            {
                if (this._shellFolder != null)
                {
                    this._shellFolder.Dispose();
                    this._shellFolder = null;
                }
            }

            if (_pIDL != IntPtr.Zero)
            {
                Marshal.FreeCoTaskMem(_pIDL);
                _pIDL = IntPtr.Zero;
            }
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="ShellPIDL"/> is reclaimed by garbage collection.
        /// </summary>
        ~ShellPIDL()
        {
            Dispose(false);
        }

        #endregion

        

        private static ShellPIDL _desktopPIDL = null;
        /// <summary>
        /// Gets the desktop PIDL.
        /// </summary>
        /// <value>The desktop PIDL.</value>
        public static ShellPIDL DesktopPIDL
        {
            get
            {
                if (_desktopPIDL == null)
                    _desktopPIDL = new ShellPIDL(Shell32.CSIDL.CSIDL_DESKTOP);
                
                return _desktopPIDL;
            }
        }

        /// <summary>
        /// Gets the parent.
        /// </summary>
        /// <value>The parent.</value>
        public ShellPIDL Parent
        {
            get
            {
                // Desktop doesn't have a parent

                if (IsDesktop)
                    return null;

                IntPtr parentPIDL = Shell32.ILClone(_pIDL);

                if (!Shell32.ILRemoveLastID(parentPIDL))
                {
                    Marshal.FreeCoTaskMem(parentPIDL);
                    throw (new ApplicationException("Failed to get parent pidl."));
                }

                return new ShellPIDL(parentPIDL);
            }
        }

        private ShellFolder _shellFolder;
        /// <summary>
        /// Gets the shell folder.
        /// </summary>
        /// <value>The shell folder.</value>
        public ShellFolder ShellFolder
        {
            get
            {
                /*if (IsDesktop)
                    return ShellFolder.DesktopShellFolder;*/

                if (_shellFolder == null)
                    _shellFolder = new ShellFolder(this);

                return _shellFolder;
            }
        }

        private IntPtr _pIDL;
        /// <summary>
        /// Gets the PIDL.
        /// </summary>
        /// <value>The PIDL.</value>
        public IntPtr PIDL
        {
            get { return _pIDL; }
        }

        private string _path;
        /// <summary>
        /// Gets the path.
        /// </summary>
        /// <value>The path.</value>
        public string Path
        {
            get
            {
                if (_path == null)
                    _path = GetPath(_pIDL);
                
                return _path;
            }
        }

        /// <summary>
        /// Gets the path.
        /// </summary>
        /// <param name="pIDL">The p IDL.</param>
        /// <returns></returns>
        private static string GetPath(IntPtr pIDL)
        {
            StringBuilder sb = new StringBuilder(256);
            Shell32.SHGetPathFromIDList(pIDL, sb);
            return sb.ToString();
        }

        /// <summary>
        /// Gets the PIDL.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns></returns>
        private static IntPtr GetPIDL(string path)
        {
            if (string.IsNullOrEmpty(path))
                throw(new ArgumentNullException("path"));
            
            if(!Directory.Exists(path) && !File.Exists(path))
            {
                throw (new FileNotFoundException("The path \"" + path + "\" could not be found."));
            }

            IntPtr ret = IntPtr.Zero;
            //Shell32.IShellFolder shellFolder = null;

            try
            {
                
                //int hres = Shell32.SHGetDesktopFolder(ref shellFolder);
                //if (hres != 0) Marshal.ThrowExceptionForHR(hres);

                uint eaten = 0;
                Shell32.SFGAO attribs = Shell32.SFGAO.SFGAO_FOLDER;

                ShellFolder.DesktopShellFolder._interface.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, path, out eaten, out ret, ref attribs);
            }
            finally
            {
                //if (shellFolder != null) Marshal.ReleaseComObject(shellFolder);
            }

            return ret;
        }

        /// <summary>
        /// Gets the ancestors.
        /// </summary>
        /// <param name="includeSelf">if set to <c>true</c> to include the current object in the array.</param>
        /// <returns></returns>
        /// <value>The ancestors.</value>
        public ShellPIDL[] GetAncestors(bool includeSelf)
        {
            List<ShellPIDL> list = new List<ShellPIDL>();

            if (includeSelf)
                list.Add(this);

            ShellPIDL pidl = Parent;

            while (pidl != null)
            {
                //list.Add(new ShellPIDL(pidl.PIDL));
                list.Add(pidl);
                pidl = pidl.Parent;
            }

            if (list.Count > 0)
                return list.ToArray();
            
            return null;
        }
    }
}
